#include <err.h>
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include <unistd.h>
#include <sys/param.h>
#include <sys/resource.h>

#include "../util.h"
#include "../getopt_helper.h"

static struct option const LONG_OPTIONS[] = {
	{ "adjustment", required_argument, NULL, 'n' },
	GETOPT_END_DECL,
};

static const char usage_str[] =
    "Usage: %s [OPTION] [COMMAND [ARG]...]\n"
    "Run COMMAND with an adjusted niceness, which affects process "
    "scheduling.\n"
    "With no COMMAND, print the current niceness.  Niceness values range "
    "from\n"
    "-20 (most favorable to the process) to 19 (least favorable to the "
    "process).\n"
    "\n"
    "Mandatory arguments to long options are mandatory for short options "
    "too.\n"
    "  -n, --adjustment=N   add integer N to the niceness (default 10)\n"
    "      --help           display this help and exit\n"
    "      --version        output version information and exit\n"
    "\n"
    "NOTE: your shell may have its own version of nice, which usually "
    "supersedes\n"
    "the version described here.  Please refer to your shell's documentation\n"
    "for details about the options it supports.";

#define DEFAULT_PRIO 10

typedef int prio_t;

static inline prio_t str2prio(const char* str);

static enum flag prio_given = NO;

int
main(int argc, char* argv[])
{
	int    i;
	prio_t current;
	prio_t prio = DEFAULT_PRIO;

	for (i = 1; i < argc; optind = 0) {
		char*  arg   = argv[i];
		int    _i    = i - 1;
		int    _argc = argc - _i;
		char** _argv = argv + _i;

		if (*(arg++) == '-') {
			/*
			 * -10  set priority to 10
			 * --9  sub 9
			 * -+15 add 15
			 */
			char* ptr = arg;
			ptr += (*ptr == '-' || *ptr == '+');

			if (isdigit(*ptr)) {
				prio = str2prio(arg);
				++i;
				continue;
			}
		}

		int ch;
		ch = getopt_long(_argc, _argv, "n:", LONG_OPTIONS, NULL);
		i  = optind + _i;

		switch (ch) {
		case 'n':
			prio = str2prio(optarg);
			break;

		case -1:
			goto end_loop;

			GETOPT_END_CASE
		}
	}
end_loop:

	errno	= 0;
	current = getpriority(PRIO_PROCESS, 0);
	if (errno) err(EXIT_CANCELED, "getpriority");

	if (i == argc) {
		if (!prio_given) {
			printf("%d\n", current);
			return 0;
		}
		errx(EXIT_CANCELED,
		     "a command must be given with an adjustment");
	}

	if (setpriority(PRIO_PROCESS, 0, current + prio)) warn("setpriority");

	execvp(argv[i], &argv[i]);
	err((errno == ENOENT) ? EXIT_ENOENT : EXIT_CANNOT_INVOKE,
	    "'%s'",
	    argv[i]);
}

static inline prio_t
str2prio(const char* str)
{
	char* e;

	long int n = strtol(str, &e, 10);

	if (*e) err(EXIT_CANCELED, "%s", str);

	prio_given = YES;
	return (prio_t)MAX(PRIO_MIN, MIN(n, PRIO_MAX));
}
